//! pGina winLDAP plugin.
/*!

Some code has been taken from ldapAuth_plus (http://www.pgina.org/index.php/Plugins:LDAP_Auth). 
Please observe their licenses as well if you use develop based upon this code. 


LTIA - Applied Information Technology Lab
UNESP - Univ Estadual Paulista, Bauru

*/

#include "stdafx.h"
#include "ctype.h"
#include "winLDAP.h"
#include "ConfigureDialog.h"
#include ".\winLDAP.h"
#include <iostream>
#include <wincrypt.h>

#pragma comment(lib, "crypt32.lib")
//#include "md5.h"




#ifdef _DEBUG
#define new DEBUG_NEW

// For detecting memory leaks (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/memleaks.asp)
#define _CRTDBG_MAP_ALLOC 1
#include <stdlib.h>
#include <crtdbg.h>

#endif

// globo vars
lpfnDllCrypt plugCrypt = NULL;
lpfnDllHash plugHash = NULL;


#ifdef _DEBUG
// Log a debug string to a file in the c:\ root
void debugOut(WCHAR *string,...)
{
	WCHAR filename[256];
	int number = 0;
	va_list argList;
	time_t ltime;
	FILE *debugFile = NULL;                       
	WCHAR strForConcat[512] = {L"pgina.ldapauth->trace: "};

	time(&ltime);

	if(debugFile == NULL)
	{
		number = (int) time(NULL);
		swprintf(filename,L"c:\\pgina_debug%d.txt",number);
		debugFile = _wfopen(filename,L"a+");
	}

	va_start(argList,string);
	fwprintf(debugFile,L"@%.*s: ",24,ctime(&ltime));
	vfwprintf(debugFile,string,argList);
	fwprintf(debugFile,L"\n");
	fflush(debugFile);
	wcscat(strForConcat,string);
	wcscat(strForConcat,L"\n");
	OutputDebugString(strForConcat);
}
#endif

/* pluginError Error reporting function
	A general catch-all function for capturing errors. This way, I can send errors somewhere, and how they are handled
	can be changed more easily. Severity will be made up as I go along for now, but let's say the following:
	1	Debug
	2	Informational
	3	Terminal
*/
#ifdef _DEBUG
void pluginError(WCHAR *string, int severity)
{
	WCHAR filename[256];
	int number = 0;
	va_list argList;
	time_t ltime;
	FILE *debugFile = NULL;                       
	WCHAR strForConcat[512] = {L"pgina.ldapauth->trace: "};

	time(&ltime);

	if(debugFile == NULL)
	{
		number = (int) time(NULL);
		swprintf(filename,L"c:\\pgina_debug%d.txt",number);
		debugFile = _wfopen(filename,L"a+");
	}

	va_start(argList,string);
	fwprintf(debugFile,L"@%.*s: ",24,ctime(&ltime));
	vfwprintf(debugFile,string,argList);
	fwprintf(debugFile,L"\n");
	fflush(debugFile);
	wcscat(strForConcat,string);
	wcscat(strForConcat,L"\n");

	// OutputDebugString sends a printf-like string out to a listening debugger
	// It's a great place to put secret messages or information for a real-time trace

	OutputDebugString(strForConcat);
	
}
#endif

// The following two functions are from Nate's PAM plugin to convert
// between UNICODE and ASCII and back
char * convert_to_char(const TCHAR *from)
{
	char *to = (char *) GlobalAlloc(NULL,1024);
	
	WideCharToMultiByte(CP_ACP,
						NULL,
						(LPCWSTR)from,
						-1,
						to,
						1024,
						NULL,
						NULL);
	
	return to;
}

TCHAR* convert_to_wchar(const char *from)
{
	TCHAR *to;
	
	to = (TCHAR *) GlobalAlloc(NULL,1024*sizeof(TCHAR)+1);
	MultiByteToWideChar(CP_ACP,
						NULL,
						(LPCSTR)from,
						(int) strlen(from)+1,
						(LPWSTR)to,
						1024*sizeof(TCHAR) + 1);					

	return to;
}


char * _get_salt (char salt[16])
{
  int i;
  int j;

  srand ((UINT) time (NULL));

  for (j = 0; j < 2; j++)
    {
      i = rand () % 3;
      switch (i)
	{
	case 0:
	  i = (rand () % (57 - 46)) + 46;
	  break;
	case 1:
	  i = (rand () % (90 - 65)) + 65;
	  break;
	case 2:
	  i = (rand () % (122 - 97)) + 97;
	  break;
	}
      salt[j] = i;
    }
  salt[2] = '\0';
  return salt;
}



using namespace std;

//Function to convert unsigned char to string of length 2

// Enter a message into the event viewer
void plugError(LPCWSTR * errorStr, LPVOID rawData, DWORD eventID)
{
// function to record errors to the system event log
	HANDLE logH;
	DWORD rawSize;

	//logH = OpenEventLog(NULL, L"ldapAuth"); // open handle to event log
	logH = RegisterEventSource(NULL, L"ldapAuth");
	if (logH == NULL)
		return;

	rawSize = sizeof(rawData);

	ReportEvent(
				logH, // previous handle
				EVENTLOG_ERROR_TYPE, //it's an error (not a warning)
				0, // something about the category in the message file
				eventID, // something about the event in the message file
				NULL, // we don't need the user's security id
				1, // number of strings we'll pass -- we only want the one
				rawSize, // bytes of raw data to put in the log
				errorStr, //the error string
				rawData); // raw data == error

	DeregisterEventSource(logH);
}

bool ldapDoBindTest(LPTSTR dn, LPTSTR Password, LDAP *gloLdapStruct)
{
	// function that actually does the work -- I found that I was calling this same code several places, so I've consolidated
	ULONG ldapStat;
	DWORD useSSL;

	// Check valid LDAP structure given
	if (gloLdapStruct == NULL)
		return false;

	useSSL = regReadDword(L"useSSL");
	if (1 == useSSL)
		ldapStat = ldap_bind_s(gloLdapStruct, dn, Password, 128);
	else
		ldapStat = ldap_simple_bind_s(gloLdapStruct, dn, Password); // We use _s to that it will be synchronous & we can find out NOW

	if (ldapStat == LDAP_SUCCESS)
		return (true);
	else {
#ifdef _DEBUG
		WCHAR debug[256];
		swprintf(debug, L"LDAP bind failed with error: 0x%x.", ldapStat);
		pluginError(debug, 1);
#endif
		return (false);
	}
}

bool ldapTestMap(LPTSTR Username, LPTSTR Password, WCHAR * searchContext, WCHAR * ldpContext, LDAP *gloLdapStruct, WCHAR * dn)
{
	// the original ldap auth method -- and still the simplest!
    bool res;
	WCHAR * ldapPrePend;
	WCHAR * ldapAppend;

	// get some variables from the registry for added fun
	ldapPrePend = regReadString(L"ldapPrePend");
	if (NULL == ldapPrePend)
		return false;
	if(NULL == searchContext)
	{
		ldapAppend = regReadString(L"ldapAppend");
		if (NULL == ldapAppend)
		{
			ldapAppend = regReadString(L"ldapContext0");
			if (NULL == ldapAppend)
			{
				free(ldapPrePend);
				return false;
			}
		}
	}
	else
		ldapAppend = wcsdup(searchContext);

	// construct our dn string of cn=Username,ou=users,o=mu
	wcscpy(dn, ldapPrePend);
	wcscat(dn, Username);
	wcscat(dn, L",");
	wcscat(dn, ldapAppend);

	res = ldapDoBindTest(dn, Password, gloLdapStruct);
	if(res)
	{
		// copy our ldapAppend into ldapContext
		if (wcslen(ldapAppend) < 256) // a lazy way to prevent a buffer overflow
			wcscpy(ldpContext, ldapAppend);
	}

	free (ldapAppend);
	free (ldapPrePend);

	return res;
}

WCHAR * getContext(WCHAR * DN)
{
	UINT i = 0;
	WCHAR * pdest;

//  wcschr returns the position of the first comma in the string
	pdest = wcschr(DN, ',');
//  We then need to increment that because we want what comes AFTER the comma
	pdest++;

#ifdef DEBUG
	WCHAR debug[256];
	swprintf(debug, L"Context determined to be: %s\n", pdest);
	debugOut(debug);
#endif
	return pdest;
}

bool ldapSearchForDN(LPTSTR Username, WCHAR * DN, WCHAR * ldpContext, LDAP *gloLdapStruct)
// Split this function into ldapSearchForDNGeneric when nldapContextLookup was added (Marc)
{
	WCHAR * regTemp = NULL;
	WCHAR * searchContext = NULL;
	WCHAR ldapFilter[256];
	WCHAR * ldapAdminUsername = NULL;
	WCHAR * ldapAdminPassword = NULL;
	DWORD useSSL;
    bool res;

	searchContext = regReadString(L"ldapContext0");
	if (NULL == searchContext)
		return false;

	// construct filter -- consult the registry for ldapFilter key. If populated, attempt to use it. 
	// Otherwise, assume a filter (that works at Miami, at least) of uid=%s
	// Filters should be something like (&(objectClass=person)(cn=%s)) -- the %s will be needed b/c this string
	// is passed into an sprintf and the %s is changed to the user's name; hopefully this works in the registry
	// but on the safe side, reg_expand_string should NOT be used!
	regTemp = regReadStringDefault(L"ldapFilter", L"(uid=%s)");

	// Now that we have a filter, we do an sprintf to put the username where the %s is
	//  we give 5 args, so that up to five %s will get the username
	swprintf(ldapFilter, regTemp, Username,Username,Username,Username,Username);

    free(regTemp);

	// This is cool -- if the registry key is empty, it will set the point to NULL
	// if the pointer is NULL, it's just passed NULL into the bind, so it will be anon
	ldapAdminUsername = regReadString(L"ldapAdminUsername");
	ldapAdminPassword = ReadProtectedStringValueFromRegistry(L"ldapAdminPassword");

	useSSL = regReadDword(L"useSSL");

	res = ldapSearchForDNGeneric(DN, searchContext, ldpContext, gloLdapStruct, ldapFilter, ldapAdminUsername, ldapAdminPassword, useSSL);

	free(searchContext);
	if(NULL != ldapAdminUsername) free(ldapAdminUsername);
	if(NULL != ldapAdminPassword) free(ldapAdminPassword);

	return res;
}

bool ldapSearchForDNGeneric(WCHAR * DN, WCHAR * searchContext, WCHAR * ldpContext, LDAP *gloLdapStruct, 
					 WCHAR * ldapFilter, WCHAR * ldapAdminUsername, WCHAR * ldapAdminPassword, DWORD useSSL)
// Added this function to be able to use this functionality from both ldapSearchForDN and nldapContextLookup (Marc)
{
	/* vars needed */
#ifdef _DEBUG
	WCHAR debug[256];
#endif
	ULONG ldapStat;
	int ldapVer = LDAP_VERSION3;
	WCHAR * userDN;
	WCHAR * localLdapContext;
	LDAPMessage* pMsg;
	LDAPMessage* ldapFirstEntry;

	LPCWSTR myError = L"The data is the return code.";

	// let's set our ldap options
	//ldap_set_option(myLdapStruct, LDAP_OPT_DEREF, LDAP_DEREF_NEVER); // never dereference aliases
	//ldap_set_option(gloLdapStruct, LDAP_OPT_VERSION, &ldapVer); // be picky -- insist on the best

	//simple bind passes password in plain text
	if (1 == useSSL)
		ldapStat = ldap_bind_s(gloLdapStruct, ldapAdminUsername, ldapAdminPassword, 128);
	else
		ldapStat = ldap_simple_bind_s(gloLdapStruct, ldapAdminUsername, ldapAdminPassword); // We use _s to that it will be synchronous & we can find out NOW

	if (ldapStat != LDAP_SUCCESS)
	{
#ifdef _DEBUG
		if(1 == useSSL)
			wsprintf(debug, L"ldap_bind_s failed with error: 0x%x\n", ldapStat);
		else
			wsprintf(debug, L"ldap_simple_bind_s failed with error: 0x%x\n", ldapStat);
		debugOut(debug);
#endif
		return false;
	}

    if(NULL == searchContext)
		return false;

	// perform a synchronous search using previously built filter and put results in pMsg
	//ldapStat = ldap_search_s(myLdapStruct, ldapContext1, LDAP_SCOPE_SUBTREE, ldapFilter, ldapAttrs, 0, &pMsg);
	ldapStat = ldap_search_ext_s(gloLdapStruct, searchContext, LDAP_SCOPE_SUBTREE, ldapFilter, NULL, 0, NULL, NULL, NULL, 0, &pMsg);

	if (ldapStat != LDAP_SUCCESS)
	{
#ifdef _DEBUG
		wsprintf(debug, L"ldap_search_ext_s failed with error: 0x%x\n", ldapStat);
		debugOut(debug);
#endif
		ldap_msgfree(pMsg);
		return false;
	}

	// Get the first entry returned in pMsg
    ldapFirstEntry = ldap_first_entry(gloLdapStruct, pMsg);
	if (ldapFirstEntry == NULL)
	{
#ifdef _DEBUG
		wsprintf(debug, L"ldap_first_entry failed with error: 0x%x\n", ldapStat);
		debugOut(debug);
#endif
		ldap_msgfree(pMsg);
		return false;
	}

	// Get the dn of the first entry returned in pMsg -- why didn't anyone tell me it was this simple????
	userDN = ldap_get_dn(gloLdapStruct, pMsg);
	if (userDN == NULL)
	{
		ldap_msgfree(pMsg);
		return false;
	}
	wcscpy(DN, userDN);

	ldap_memfree(userDN);
    ldap_msgfree(pMsg);

	localLdapContext = getContext(DN);

	wcscpy(ldpContext, localLdapContext);

	return true;
}

WCHAR * nldapContextLookup(LPTSTR Username)
// Added this function because it's possible the ldap server is not the NetWare server. (as is the case for me)
// This function to looks up the NDS context of the account logging in (Marc)
{
	/* vars needed */
    LDAP *loLdapStruct;
	int ldapVer = LDAP_VERSION3;
	WCHAR DN[256];
	WCHAR ldapContext[256];
	WCHAR ldapFilter[256];
	WCHAR * ldapFilterOrig;
	WCHAR * ldpContext;
	WCHAR * nldapLookupContext;
    WCHAR * serverName;
	bool res;
	DWORD ldapPort = 0;
	DWORD useSSL = 0;

	LPCWSTR myError = L"The data is the return code.";

    serverName = regReadString(L"nldapContextLookupServer");
	if(NULL == serverName)
		return NULL;
	ldapPort = regReadDword(L"nldapServerPort");
	useSSL = regReadDword(L"nldapServerSSL");

	loLdapStruct = doLdapInit(serverName, ldapPort, useSSL);

    free(serverName);

	if(NULL == loLdapStruct)
		return NULL;

	nldapLookupContext = regReadString(L"nldapLookupContext");
	if (NULL == nldapLookupContext) 
	{
		ldap_unbind(loLdapStruct);
		return NULL;
	}

	ldapFilterOrig = regReadStringDefault(L"ldapFilter", L"(cn=%s)");
	swprintf(ldapFilter, ldapFilterOrig, Username,Username,Username,Username,Username);
    free(ldapFilterOrig);

	res = ldapSearchForDNGeneric(DN, nldapLookupContext, ldapContext, loLdapStruct, ldapFilter, NULL, NULL, 0);
	
	free(nldapLookupContext);
	ldap_unbind(loLdapStruct);

    if(NULL != res)
	{
	    ldpContext = _wcsdup(ldapContext);
    	return ldpContext;
	}
	else
		return NULL;
}

bool ldapTestMultiMap(LPTSTR Username, LPTSTR Password, WCHAR * ldpContext, LDAP * gloLdapStruct, WCHAR * dn)
{
	// The premise of this function is to loop through registry entries with ldap contexts.
	// We do this by creating a text string ldapContextx where x is 0-2^LONG. When the registry lookup
	// for a given ldapContextx fails, we believe we've reached the end and we terminate the loop.

	// Changed this function to call ldapTestMap instead of ldapMapGo, thereby eliminating the need for ldapMapGo (Marc)

#ifdef _DEBUG
	WCHAR debug[256];
#endif

	WCHAR * searchContext;
	WCHAR ldapContextChooser[256];
	WCHAR temp[10];
	LPCWSTR myError = L"ldapTestMultiMap failed";
	bool res;

	searchContext = regReadString(L"ldapContext0");
	if (NULL == searchContext) //safety check -- if we don't have the first context filled in, just return false
	{
#ifdef _DEBUG
		debugOut(L"ldapContext0 not filled out\n");
#endif
		plugError(&myError, NULL, LDAPREGFAIL);
		return false;
	}
	free(searchContext);

	for(LONG i = 0; i < 255; i++)
	{
#ifdef _DEBUG
		swprintf(debug, L"Starting testMultiMap run %i...\n", i);
		debugOut(debug);
#endif
		wcscpy(ldapContextChooser, L"ldapContext");
		_itow(i, temp, 10);
		wcscat(ldapContextChooser, temp);
		
		searchContext = regReadString(ldapContextChooser);
		if (NULL != searchContext)
		{
			res = ldapTestMap(Username, Password, searchContext, ldpContext, gloLdapStruct, dn);
			free(searchContext);
			if(res)
			{
#ifdef _DEBUG
				wsprintf(debug,L"ldapMapGo found user %s at context %s\n",Username, ldpContext);
				debugOut(debug);
#endif
				return true;
			}
		}
		else
			break;
	}
	return false;
}

bool ldapTestSearch(LPTSTR Username, LPTSTR Password, WCHAR * ldpContext, LDAP *gloLdapStruct, WCHAR * dn)
{
	// vars needed
	ULONG ldapStat;
	int ldapVer = LDAP_VERSION3;

	WCHAR * ldapContext1;
	DWORD useSSL;

	LPCWSTR myError = L"The data is the return code.\n";

	ldapContext1 = regReadString(L"ldapContext0");
	if (NULL == ldapContext1)
	{
		return false;
	}

	if(!ldapSearchForDN(Username, dn, ldpContext, gloLdapStruct))
		return false;

//	ldap_set_option(gloLdapStruct, LDAP_OPT_VERSION, &ldapVer); // be picky -- insist on the best

	useSSL = regReadDword(L"useSSL");
	if (1 == useSSL)
		ldapStat = ldap_bind_s(gloLdapStruct, dn, Password, 128);
	else
		ldapStat = ldap_simple_bind_s(gloLdapStruct, dn, Password); // We use _s to that it will be synchronous & we can find out NOW

	free(ldapContext1);

	if (ldapStat == LDAP_SUCCESS)
		return (true);
	else
		return (false);
}

DWORD determineLdap(void) // do we wish to use mapping or searching?
{
	DWORD dwDapMethod;

	dwDapMethod = regReadDword(L"ldapMethod");
	if (NULL == dwDapMethod)
		return MISCONFIGURED;
	else
		return dwDapMethod;
}

/* doLdapSearch 
   a standard search function for ldapauth
	doLdapSearch is an attempt to build a standard search function into ldapauth to
handle assorted searching needs. It will accept what is saught.
The ldap search filter syntax is defined in RFC something or another, so we'll
accept a filter assuming it contains just the attribute search and then we'll
do a minor rewrite to insert the username (otherwise it wouldn't be a very good
filter.
	Actually, I want users to have more flexibility, so I will not assume that, and we'll
sprintf it to put in the user's name in the filter they provide -- that way we can do a 
backwards netscape group search
	In case someone asks, yes, we could do this all in the initial search, but that
wouldn't let us determine a nonexistent user (bad username) and a user without
the correct attribute (unauthorized user). Differentiating allows us to give
better feedback
*/
bool ldapSearchCompare(LDAP *gloLdapStruct, WCHAR ldapFilter[256], WCHAR *Username)
{
	/* vars needed */
	ULONG ldapStat;
	int ldapVer = LDAP_VERSION3;
	int ldapScope;
	WCHAR * ldapContext;
	WCHAR * ldapAdminUsername;
	WCHAR * ldapAdminPassword;
	WCHAR filterTemp[256];

	LDAPMessage* pMsg;
	DWORD useSSL;

	LPCWSTR myError = L"The data is the return code.";

	ldapContext = regReadString(L"ldapContext0");
	if (NULL == ldapContext)
		return false;
	ldapAdminUsername = regReadString(L"ldapAdminUsername");
	ldapAdminPassword = ReadProtectedStringValueFromRegistry(L"ldapAdminPassword");

	// let's set our ldap options
	ldap_set_option(gloLdapStruct, LDAP_OPT_VERSION, &ldapVer); // be picky -- insist on the best 
	
	// standard bind calls
	// We use _s to that it will be synchronous & we can find out NOW
	useSSL = regReadDword(L"useSSL");
	if (1 == useSSL)
		ldapStat = ldap_bind_s(gloLdapStruct, ldapAdminUsername, ldapAdminPassword, 128);
	else 	//simple bind passes password in plain text
		ldapStat = ldap_simple_bind_s(gloLdapStruct, ldapAdminUsername, ldapAdminPassword); 

	if (ldapStat != LDAP_SUCCESS)
	{
		//printf(L"ldap_simple_bind_s failed with error: 0x%x\n", ldapStat);
		return false;
	}

	// check to see if LDAP scope is overridden in registry -- unfortunately, they'll need to know the numeric
	// code for the scope setting, but never let it be said I didn't allow for customization
	ldapScope = regReadDword(L"ldapScope");
	if (NULL == ldapScope)
		ldapScope = LDAP_SCOPE_SUBTREE;

	// rewrite filter
	// We need to insert the username as noted earlier 
	// So we assume the filter reads something like 
	// (&(uid=%s)(groupMembership=cn=labUsers,ou=orgunit,o=org)) 
	// and we should pop the Username in for the %s. Of course, if the filter is 
	// miswritten, all sorts of hurt will occur

	swprintf(filterTemp,ldapFilter,Username,Username,Username,Username,Username);
	swprintf(ldapFilter,filterTemp);

	// Along the lines of trust no one, we run the ldap filter through a function that
	// checks the filter syntax. It doesn't run it through all the validation rules, but
	// it does do some checking
	if (ldap_check_filter(gloLdapStruct, ldapFilter) != LDAP_SUCCESS)
	{
		if (ldapAdminUsername) free (ldapAdminUsername);
		if (ldapAdminPassword) free (ldapAdminPassword);
		return false;
	}

	// perform a synchronous search using previously built filter and put results in pMsg
	// we do an extended search so that we do not need to pre-specify the attribute list
	//ldapStat = ldap_search_s(myLdapStruct, ldapContext, LDAP_SCOPE_SUBTREE, ldapFilter, ldapAttrs, 0, &pMsg);
	ldapStat = ldap_search_ext_s(gloLdapStruct, ldapContext, ldapScope, ldapFilter, NULL, 0, NULL, NULL, NULL, 0, &pMsg);
	if (ldapStat != LDAP_SUCCESS)
	{
		wprintf(L"ldap_search_s failed with error: 0x%x\n", ldapStat);
		return false;
	}

	// and that should be that -- if no search results were successful, the condition
	// failed and we should return false. If we get results, we don't need to look at them
	// so we can discard them...
    ldap_msgfree(pMsg);

	// and return successful    
	if (ldapAdminUsername) free (ldapAdminUsername);
	if (ldapAdminPassword) free (ldapAdminPassword);
	
    return true;

}	

bool ldapGroupCheck(WCHAR * username, WCHAR * group, LDAP *gloLdapStruct,WCHAR * ldpContext)
{
	// function to determine if user is a member of the specified group
	//takes in name of group: returns true or false
	PWCHAR * groupMembership;
	WCHAR * groupMembershipAttr;
	UINT i = 0;
	bool member = false;
#ifdef _DEBUG
	WCHAR debug[256];
#endif

#ifdef _DEBUG
	wsprintf(debug,L"ldapGroupCheck->Group name passed in: %s\n", group);
	debugOut(debug);
#endif
	// Determine what attribute contains what I call groupMembership
	// needed for inferior schemas ;)
	groupMembershipAttr = regReadStringDefault(L"ldapGroupAttr", L"groupMembership");
	// loop through all values of the groupMembership attribute and compare with passed in groupname
	// if we match, set member to true, else loop again
	groupMembership = ldapSearchAttr(username, groupMembershipAttr, gloLdapStruct, ldpContext, false);
	if (groupMembership == NULL)
	{
#ifdef _DEBUG
		debugOut(L"NULL groupMembership\n");
#endif
		return false;
	}

	while (groupMembership[i])
	{
#ifdef _DEBUG
		wsprintf(debug,L"Group Member %i: %s\n",i,groupMembership[i]);
		debugOut(debug);
#endif
		if (_wcsicmp(group, groupMembership[i]) == 0)
		{
			member = true;
#ifdef _DEBUG
			debugOut(L"ldapGroupCheck: will be returning true\n");
#endif
			break;
		}
		else i++;
	}
	ldap_value_free(groupMembership);
	free(groupMembershipAttr);
	return member;
}

/** loopyGroupyCheck
	@brief loopyGroupyCheck is a loop to check for group membership 
*/
bool loopyGroupyCheck(WCHAR * username, LDAP *gloLdapStruct, WCHAR * ldpContext, WCHAR grpStart[256])
{
// grpStart comes in as the string that will get prepended to a number
// So we look in the registry for something like adminOK0 and then increment the number
// This tactic allows us to zip on through the registry looking for an even increasing number
// of values and as soon as we run out of registry values, we terminate the loop 
	WCHAR * testGroup;
	WCHAR groupRegEntry[256];
	WCHAR temp[16];
	UINT i = 0;
	bool isPresent = false;

	// initialize test values
	//groupRegEntry = (WCHAR *) malloc(sizeof(grpStart) + sizeof(WCHAR) + sizeof(WCHAR));
	wcscpy(groupRegEntry, grpStart);
	wcscat(groupRegEntry, L"0");
	testGroup = regReadString(groupRegEntry);
	//free (groupRegEntry);

	while (testGroup)
	{
		if (ldapGroupCheck(username, testGroup, gloLdapStruct, ldpContext)) // if the user is in the group, return true, else keep going
		{
			isPresent = true;
			break;
		}
		else
		{
			i++;
			_itow(i, temp, 10);
			//free (groupRegEntry);
			//groupRegEntry = (WCHAR *) malloc(sizeof(grpStart) + sizeof(temp) + sizeof(WCHAR));
			wcscpy(groupRegEntry, grpStart);
			wcscat(groupRegEntry, temp); //eg: userOK27
			free (testGroup);
			testGroup = regReadString(groupRegEntry);			
		}
	}
	free (testGroup);

	return isPresent;
}

bool okToLogin(WCHAR * username, pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	// allows locking workstation to specified users
	// if values are empty, allows everyone to log in
	// otherwise, allows only those with group set
	WCHAR *loginFilter;
	WCHAR *testGroup;
	bool loginOK = false;
	WCHAR *strGoAway;

	// If we have loginFilter filled in, it overrides the okToLogin stuff
	// so we will return whatever it says instead of doing the rest of this stuff
	loginFilter = regReadString(L"loginFilter");
	if (loginFilter != NULL)
	{
		loginOK = ldapSearchCompare(gloLdapStruct, loginFilter, username);
		free(loginFilter);
		return loginOK;
	}

	testGroup = regReadString(L"userOK0");
	if (NULL == testGroup)  // failsafe: if the registry key isn't filled in, let the user in
		return true;
	else if (wcsicmp(testGroup, L"all") == 0) //if the first key is set to all, let the user in
		loginOK = true;
	else
        loginOK = loopyGroupyCheck(username, gloLdapStruct, ldpContext, L"userOK");

	if (NULL != testGroup)
		free(testGroup);

	if (!loginOK) // set the message returned for bad login
	{
		strGoAway = regReadStringDefault(L"authDenyMessage", L"Your username and password are correct, but you are not authorized to use this system. Please see your system administrator.");
		settingsInfo->errorString = wcsdup(strGoAway);
		free (strGoAway);
	}
	return loginOK;
}

bool createAsAdmin(WCHAR * username, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	WCHAR * adminFilter;
	bool okAdmin = false;
	WCHAR * testGroup;

	// If we have adminFilter filled in, it overrides the adminOK stuff
	// so we will return whatever it says instead of doing the rest of this stuff
	adminFilter = regReadString(L"adminFilter");
	if (adminFilter != NULL)
	{
		okAdmin = ldapSearchCompare(gloLdapStruct, adminFilter, username);
		free (adminFilter);
		return okAdmin;
	}

	// This begins the old ldapauth behavior (that everyone hates ;) )
	testGroup = regReadString(L"adminOK0");
	if (NULL == testGroup) // failsafe: if the registry key isn't filled in, they shouldn't be admins!
		return false;
	else if (wcsicmp(testGroup, L"all") == 0) //if the first key is set to all, let all the users in as admins!
		okAdmin = true;
	else
		okAdmin = loopyGroupyCheck(username, gloLdapStruct, ldpContext, L"adminOK");

	if (NULL != testGroup)
		free(testGroup);

	return okAdmin;

}

PWCHAR * ldapSearchAttr(LPTSTR Username, WCHAR * attr, LDAP *gloLdapStruct, WCHAR * ldpContext, bool ldpOverride)
{
/*	pass ldapSearchAttr the username and the attribute name and it returns
	a pointer to an array of strings with the attributes. You shouldn't use it with
	attributes with binary values, but as a quick & dirty query, it does the job.
	Really, though, it should be rewritten to take a dn (retrieved with a previous call)
	to minimize search operations. It works on the first entry returned ONLY, so if you have more
	than one, make sure the one you want would get returned first.
*/
// 7/3/03 ldpOverride added to make function more useful
// ldpOverride tells us to do our filter raw w/o modification (in other words, this isn't an auth check

	/* vars needed */
	ULONG ldapStat;
	DWORD ldapVer = LDAP_VERSION3;
	WCHAR ldapFilter[256];
	WCHAR ldapFilterOrig[128];
	WCHAR ldapContext1[256];
	WCHAR * regTemp;

	LDAPMessage* pMsg;
	LDAPMessage* ldapFirstEntry;
	PWCHAR * userAttrPtr;
	WCHAR * a;
	BerElement *ber;
	PWCHAR ldapAttrs[] = {attr, NULL};
#ifdef _DEBUG
	WCHAR debug[256];
#endif

	LPCWSTR myError = L"The data is the return code.";

//  these lines seem superfluous now that they are repeated in if(ldpOverride==false) block
//	regTemp = regReadStringDefault(L"ldapFilter", L"(uid=%s)");
//	swprintf(ldapFilter, regTemp, Username);
//	free (regTemp);

	if (ldpOverride == false)
	{
		regTemp = regReadStringDefault(L"ldapFilter", L"(uid=%s)");
		wcscpy(ldapFilterOrig, regTemp);
		free (regTemp);
		swprintf(ldapFilter, ldapFilterOrig, Username,Username,Username,Username,Username);
	}
	else wcscpy(ldapFilter, Username);

	if (ldpContext == NULL)
	{
		regTemp = regReadString(L"ldapContext0");
		if (NULL == regTemp)
		{
#ifdef _DEBUG
			debugOut(L"ldapSearchAttr could not find first LDAP context\n");
#endif
			return NULL;
		}
		else 
			wcscpy(ldapContext1, regTemp);
		free(regTemp);
	}
	else wcscpy(ldapContext1,ldpContext);

	// perform a synchronous search using previously built filter and put results in pMsg
	ldapStat = ldap_search_ext_s(gloLdapStruct, ldapContext1, LDAP_SCOPE_SUBTREE, ldapFilter, ldapAttrs, 0, NULL, NULL, NULL, 0, &pMsg);
	if (ldapStat != LDAP_SUCCESS)
	{
#ifdef _DEBUG
		swprintf(debug,L"ldap_search_s failed with error: 0x%x\n", ldapStat);
		debugOut(debug);
#endif
		return NULL;
	}

	// Get the first entry returned in pMsg
    ldapFirstEntry = ldap_first_entry(gloLdapStruct, pMsg);
	//debugOut(L"got first entry");
	if (ldapFirstEntry == NULL)
	{
		ldapStat = LdapGetLastError();

#ifdef _DEBUG
		swprintf(debug,L"ldap_first_entry failed with error: 0x%x\n", ldapStat);
		debugOut(debug);
#endif
		return NULL;
	}

	// before we can get the values, we must get the attributes
	a = ldap_first_attribute( gloLdapStruct, ldapFirstEntry, &ber);
	//debugOut(L"got first attribute");

	userAttrPtr = ldap_get_values(gloLdapStruct, ldapFirstEntry, a);
	//debugOut(L"got values");

	ber_free(ber, 0);
    ldap_msgfree(pMsg);
	return userAttrPtr;
}

/* mapMacOSHome
	@brief Utility function for setHomeDir
	setHomeDir is a utility function called by setHomeDir for determining 
	the home directory when using MacOS X as LDAP provider
*/
WCHAR * mapMacOSHome(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	WCHAR * stripMe = NULL;
	PWCHAR * homeDirectoryValues;
	WCHAR * homeAttribute = NULL;
	WCHAR rawHomeDirectoryBuffer[256];
	WCHAR homeDirectoryBuffer[256];
	WCHAR * index;
	WCHAR * this_char;

	homeAttribute = regReadStringDefault(L"homeAttribute", L"homeDirectory");

	homeDirectoryValues = ldapSearchAttr(settingsInfo->Username,homeAttribute,gloLdapStruct,ldpContext, false);
	free (homeAttribute);
	wcscpy(rawHomeDirectoryBuffer, homeDirectoryValues[0]);
	ldap_value_free(homeDirectoryValues);

	// this is a kludge to allow transition from MacOSX homedirs to UNC/Samba homedirs
	if(wcsncmp(L"\\\\", rawHomeDirectoryBuffer, wcslen(L"\\\\")) == 0)
		return wcsdup(rawHomeDirectoryBuffer);

	// Now we have something like /Network/Servers/Servername/sharepoint/username

	stripMe = regReadStringDefault(L"homeStripString", L"/Network/Servers/");
	index = rawHomeDirectoryBuffer;
	if(NULL != stripMe)
	{
		// We could just move ahead X characters: parsedDirectory += wcslen(stripMe);
		//  but we should probably do this the *smart* way. Check to see if the string in
		//  stripMe is there first, then move past it if it is
		if(wcsncmp(stripMe, rawHomeDirectoryBuffer, wcslen(stripMe)) == 0)
			index += wcslen(stripMe);
		free (stripMe);
	}
	do {
		this_char = wcsstr(index, L"/");
		if(this_char)
			*this_char = L'\\';
	} while(this_char != NULL);

	wcscpy(homeDirectoryBuffer, L"\\\\");
	wcscat(homeDirectoryBuffer, index);

	return wcsdup(homeDirectoryBuffer);
}

/*	mapNDSHomeLookup
	@brief mapNDSHomeLookup queries NDS for basic attributes
*/
void mapNDSHomeLookup(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext, WCHAR * volObj, WCHAR * homeSubDir)
{
	WCHAR * homeAttribute = NULL;
	WCHAR ndsHomeDirVals[5][256];
	PWCHAR *homeDirectoryValues = NULL;
	WCHAR * token;
	int i = 0;

	homeAttribute = regReadStringDefault(L"homeAttribute", L"homeDirectory");

	// now get the homedir value
	homeDirectoryValues = ldapSearchAttr(settingsInfo->Username,homeAttribute,gloLdapStruct,ldpContext, false);
	if (homeDirectoryValues == NULL)
	{
		free (homeAttribute);
		return;
	}
	// set things up to be a little more robust....
	wcscpy(ndsHomeDirVals[0], L"");
	wcscpy(ndsHomeDirVals[1], L"");
	wcscpy(ndsHomeDirVals[2], L"");
	// this will be something ugly like cn=SAS1-Users,ou=SAS,o=mu#0#SAS/COOPERMJ
	// so begin to parse
	// pull apart ldapContext and configure as NDS context
	token = wcstok(*homeDirectoryValues,L"#");
	while (token != NULL)
	{
		wcscpy(ndsHomeDirVals[i],token);
		token = wcstok(NULL,L"#");
		i++;
	}
	ldap_value_free(homeDirectoryValues);
	// this should give us [0] = SAS1-Users, [1] = 0, [2] = SAS/COOPERMJ
	// we'll copy these values to variables with real names
	wcscpy(volObj, ndsHomeDirVals[0]);
	wcscpy(homeSubDir, ndsHomeDirVals[2]);
	free(homeAttribute);
}

/*	mapNDSHome
	@brief Uility function for setHomeDir
	mapNDSHome is a utility function called by setHomeDir for determining
	the home directory when using NDS/eDirectory as the LDAP provider
*/
WCHAR * mapNDSHome(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	WCHAR * homeDirectory;
	LPTSTR parsedDirectory = NULL;
	WCHAR * appendMe = NULL;
	TCHAR *this_char = NULL;
	int i = 0, y=0;

	WCHAR volObjCtx[256];
	WCHAR volObjCN[256];
	WCHAR volObj[256];
	WCHAR homeSubDir[256];
	WCHAR * token;
	WCHAR * ndsDNSDomain = NULL;
	PWCHAR * store;
	WCHAR volServDN[256];
	WCHAR volObjVol[256];
	WCHAR volObjFullPath[256];

	//Query NDS for values
#ifdef _DEBUG
	// memory leak test
	debugOut(L"Memory checkpointing before mapNDSHomeLookup...\n");
	_CrtMemState s1;
	_CrtMemCheckpoint( &s1 );
#endif
	mapNDSHomeLookup(settingsInfo, gloLdapStruct, ldpContext, volObj, homeSubDir);

#ifdef _DEBUG
	debugOut(L"Memory comparingfrom mapNDHomeLookup\n");
	_CrtMemState s2;
	_CrtMemState s3;
	_CrtMemCheckpoint( &s2 );
	if ( _CrtMemDifference( &s3, &s1, &s2) ) 
	{
		debugOut(L"Memory leak found in mapNDSHomeLookup. Details follow: \n");
        _CrtMemDumpStatistics( &s3 );
		_CrtDumpMemoryLeaks();
	}
#endif

	// this should give us volObj = SAS1-Users, homeSubDir = SAS/COOPERMJ

	// now we need to find out what SAS1-Users is
	// first we need to end the string at the comma...
	token = wcstok(volObj,L",");
	wcscpy(volObjCN, token);
	// then we establish our search base with what came after the comma
	// commence ugly, ugly code -- someone give me a hand here?
	wcscpy(volObjCtx, L"");
	while (token != NULL)
	{
		token = wcstok(NULL,L","); // search for ,
		if (token != NULL)
		{
			wcscat(volObjCtx, token);
			wcscat(volObjCtx, L",");
		}
	}
	y = (int) wcslen(volObjCtx);
	volObjCtx[y-1] = NULL;
	// end ugly, ugly code -- resume normal ugly code
	// now volObjCN contains the CN and volObjCtx contains the context
	// then query for hostServer
	store = ldapSearchAttr(volObjCN, L"hostServer", gloLdapStruct, volObjCtx, true);
	wcscpy(volServDN,*store);
	ldap_value_free(store);
	// okay, so now ndsHomeDirVals[3] has the dn of the server, but stupid Novell doesn't store
	// dns name as an attribute, but gives us silly things labelled networkAddress that I can figure out
	// so we'll cheat and get the CN and append a "common" dns suffix.
	token = wcstok(volServDN,L",");
	wcscpy(volServDN,token);

	token = wcstok(volServDN,L"=");
	token = wcstok(NULL,L"=");
	wcscpy(volServDN,token);

	// ndsHomeDirVals[1] now has the pure cn of the server
	ndsDNSDomain = regReadString(L"ndsServerDomain");
	if (NULL == ndsDNSDomain) 
		return NULL;
	wcscat(volServDN, ndsDNSDomain);
	free(ndsDNSDomain);
	// then get volume
	store = ldapSearchAttr(volObjCN, L"hostResourceName", gloLdapStruct, volObjCtx, true);
	wcscpy(volObjVol,*store);
	ldap_value_free(store);

	swprintf(volObjFullPath, L"\\\\%s\\%s\\%s",volServDN,volObjVol,homeSubDir);
	
	// ndsHomeDirVals[0] should now be a UNC path to home dir
	homeDirectory = wcsdup(volObjFullPath);

	return homeDirectory;
}
/* mapSambaHome
	@brief Utility function for setHomeDir
	mapSambaHome is a utility function called by setHomeDir for determining 
	the home directory when getting home directory from smbHome attribute in ldap
*/
WCHAR * mapSambaHome(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	PWCHAR * homeDirectoryValues;
	WCHAR * homeDirectory;
	WCHAR * homeAttribute = NULL;

	homeAttribute = regReadStringDefault(L"homeAttribute", L"smbHome");

	homeDirectoryValues = ldapSearchAttr(settingsInfo->Username,homeAttribute,gloLdapStruct,ldpContext, false);
	homeDirectory = wcsdup(homeDirectoryValues[0]);
	ldap_value_free(homeDirectoryValues);
	// Now we have something like \\server\share
	
	free (homeAttribute);
	return homeDirectory;
}

WCHAR * setSambaHomeDrive(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
// added to get homeDriveLetter from ldap  (Marc)
{
	WCHAR * homeDrive;
	WCHAR * homeDriveAttribute = regReadStringDefault(L"homeDriveAttribute", L"homeDrive");
	PWCHAR * homeDriveValues = ldapSearchAttr(settingsInfo->Username,homeDriveAttribute,gloLdapStruct,ldpContext, false);
	free (homeDriveAttribute);
	homeDrive = wcsdup(homeDriveValues[0]);
	ldap_value_free(homeDriveValues);
	
	return homeDrive;
}

/* setHomeDir
	@brief Utility function for populateUserInfo
	setHomeDir returns to populateUserInfo the information about the home directory. Thanks to NDS parsing needs,
	this process had to be broken up
*/
void setHomeDir(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{

	DWORD homeAutomount;
	WCHAR *homeDirectory = NULL;
	WCHAR * appendMe = NULL;
	WCHAR * driveLetter = NULL;
	WCHAR * homeAttribute = NULL;
	WCHAR buffer[30000];
	WCHAR *this_char = NULL;
	int i = 0, y = 0;

	// begin Nate code for home directory support
	homeAutomount = regReadDword(L"homeAutomount");

	if(homeAutomount >= 1)
	{
		// Auto home directory mounting
		appendMe = regReadString(L"homeAppendString");	// Don't append anything by default
		driveLetter = regReadString(L"homeDriveLetter");

		switch (homeAutomount)
		{
			// 7/1/03 Switched to switch to allow for multiple methods of home directories
			// 2004/05/05 added mapSambaHome and changed mapMacOSHome and mapNDSHome to return homeDirectory in UNC format

		case MacOSX: // MacOSX (pronounce it and it sounds funny)
			homeDirectory = mapMacOSHome(settingsInfo, gloLdapStruct, ldpContext);
			break;

		case NDS: // that NDS thing: much, much uglier than what you just saw
#ifdef _DEBUG
			// memory leak test
			debugOut(L"Memory checkpointing before mapNDSHome...\n");
			_CrtMemState s1;
			_CrtMemCheckpoint( &s1 );
#endif
			homeDirectory = mapNDSHome(settingsInfo, gloLdapStruct, ldpContext);
#ifdef _DEBUG
			// memory leak test
			debugOut(L"Memory comparing\n");
			_CrtMemState s2;
			_CrtMemState s3;
			_CrtMemCheckpoint( &s2 );
			if ( _CrtMemDifference( &s3, &s1, &s2) ) 
			{
				debugOut(L"Memory leak found in mapNDSHome. Details follow: \n");
                _CrtMemDumpStatistics( &s3 );
				_CrtDumpMemoryLeaks();
			}
#endif
			break;

		case Samba: // get a homedir already in \\server\share format from smbHome attribute in LDAP
			homeDirectory = mapSambaHome(settingsInfo, gloLdapStruct, ldpContext);
            if(NULL == driveLetter)  // if we didn't find a drive letter in the reg, try from ldap  (Marc)
				driveLetter = setSambaHomeDrive(settingsInfo, gloLdapStruct, ldpContext);
			break;

		default: // assume nothing
			homeDirectory = NULL;
			break;

		}

		if(NULL == driveLetter) // if driveletter not set in registry nor ldap, set it to default (Marc)
			driveLetter = wcsdup(L"I");

		if(homeDirectory)
		{
			memset(buffer,'\0',sizeof(buffer));				
			if(settingsInfo->mapPaths)
				if(NULL != appendMe)
					swprintf(buffer,L"%s:%s%s;%s",driveLetter,homeDirectory,appendMe,settingsInfo->mapPaths);
				else
					swprintf(buffer,L"%s:%s;%s",driveLetter,homeDirectory,settingsInfo->mapPaths);
			else
				if(NULL != appendMe)
					swprintf(buffer,L"%s:%s%s",driveLetter,homeDirectory,appendMe);
				else
					swprintf(buffer,L"%s:%s",driveLetter,homeDirectory);
			
			free(homeDirectory);
			settingsInfo->mapPaths = wcsdup(buffer);  // Memory leak?  (Marc) 
			//wcscpy(settingsInfo->mapPaths, buffer); // Memory leak or no, not doing it that way causes a crash -- mappaths has not allocated memory
			//end nate code
		}
	}	

	// failsafe cleanup, largely b/c of detected memory leaks
	if (appendMe) free(appendMe);
	if (driveLetter) free(driveLetter);
	if (homeAttribute) free(homeAttribute);
}

void populateUserInfo(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)
{
	// This is where we set attributes on the Windows user based upon
	// what we find in LDAP-land
	// Note that we only use the first description attribute

#ifdef _DEBUG
	WCHAR debug[256];
#endif
	WCHAR * fullNameAttribute = NULL;
    WCHAR * descriptionAttribute = NULL;
	PWCHAR * fullName;
	PWCHAR * description;

    fullNameAttribute = regReadStringDefault(L"fullNameAttribute", L"fullName");
	fullName = ldapSearchAttr(settingsInfo->Username, fullNameAttribute, gloLdapStruct, ldpContext, false);
    free(fullNameAttribute);
	if (fullName)
	{
		if(settingsInfo->userFullName)
		{
#ifdef _DEBUG
		   wsprintf(debug, L"settingsInfo->userFullName not NULL in populateUserInfo. Set to %s\n", settingsInfo->userFullName);
		   debugOut(debug);
#endif
		   free(settingsInfo->userFullName);
		}
		settingsInfo->userFullName = wcsdup(fullName[0]);
		ldap_value_free(fullName);
	}

	descriptionAttribute = regReadStringDefault(L"descriptionAttribute", L"description");
	description = ldapSearchAttr(settingsInfo->Username, descriptionAttribute, gloLdapStruct, ldpContext, false);
    free(descriptionAttribute);
	if (description)
	{
		settingsInfo->userDescription = _wcsdup(description[0]);
		ldap_value_free(description);
	}

	// broke setHomeDir into a separate function for sanity's sake
	setHomeDir(settingsInfo,  gloLdapStruct, ldpContext);
}

void setNDSReg(WCHAR * ndsTree, WCHAR * strNDSContext)
{
	LONG lResult;
	HKEY hKey;

	lResult = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
							 L"SYSTEM\\CurrentControlSet\\Services\\NetwareWorkstation\\Parameters\\Trees",
							 0,
							 NULL,
							 REG_OPTION_NON_VOLATILE,
							 KEY_ALL_ACCESS,
							 NULL,	 // This field can be used to set Security..?
							 &hKey,
							 NULL);	 // This value can be used to determine if the key existed or not..
									 //  but we dont care as long as it opens.. i guess.

	if(lResult == ERROR_SUCCESS)
	{
		lResult = RegSetValueEx(hKey,ndsTree,0,REG_SZ,
								(CONST BYTE *) strNDSContext,
								(DWORD)wcslen(strNDSContext) * 4);
	}
	
#ifdef _DEBUG
	if (lResult != ERROR_SUCCESS)
		debugOut(L"Failed to set registry for Novell client\n");
#endif
}

void confNDSClient(WCHAR * ndsTree, WCHAR * ldpContext)
{
	WCHAR strWrkContext[256]; // copy of ldpContext that was passed in
	WCHAR strNDSContext[256] = {L""}; // context in NDS form
	WCHAR * token;
	UINT intCntxLen;
#ifdef _DEBUG
	WCHAR debug[256];
#endif

	wcscpy(strWrkContext,ldpContext);
	// pull apart ldapContext and configure as NDS context
	token = wcstok(strWrkContext,L",");
	while (token != NULL)
	{
		wcscat(strNDSContext,token);
		wcscat(strNDSContext,L".");
		token = wcstok(NULL,L",");
	}
	intCntxLen = (UINT) wcslen(strNDSContext);
	strNDSContext[intCntxLen - 1] = NULL;
#ifdef _DEBUG
	wsprintf(debug,L"NDS Context: %s\n",strNDSContext);
	debugOut(debug);
#endif

	// set registry to NDS context
	setNDSReg(ndsTree,strNDSContext);
}

BOOL whoIsGroup(WCHAR * strAdminGroup, DWORD rid)
{
	// Much of the following code is from 
	// http://support.microsoft.com/default.aspx?scid=KB;en-us;q157234
	// I've left much of the cruft in as reference for future needs

//	PUSER_MODALS_INFO_2 umi2;
//	NET_API_STATUS nas;

//	UCHAR SubAuthorityCount;
	PSID pSid;
	SID_NAME_USE snu;

	WCHAR DomainName[DNLEN+1];
	DWORD cchDomainName = DNLEN;
	BOOL bSuccess = FALSE; // assume failure
	WCHAR computerName[256];
	DWORD cnSize;
	DWORD cchName = UNLEN;

	GetComputerName(computerName, &cnSize);


	SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
	// 
	// Sid is the same regardless of machine, since the well-known
	// BUILTIN domain is referenced.
	// 

	if(AllocateAndInitializeSid(
			&sia,
			2,
			SECURITY_BUILTIN_DOMAIN_RID,
			rid,
			0, 0, 0, 0, 0, 0,
			&pSid
			)) {

		bSuccess = LookupAccountSidW(
				computerName,
				pSid,
				strAdminGroup,
				&cchName,
				DomainName,
				&cchDomainName,
				&snu
				);

		FreeSid(pSid);
	}

	return bSuccess;
}

// ldapNegotiate is to configure LDAP settings after the connection is made.
void ldapNegotiate(LDAP *ld)
{
#ifdef _DEBUG
	WCHAR debug[256];
#endif
	DWORD dwdLdapTimeout;
	ULONG ldpError;

	// default timeout for ldap failure is 120 seconds -- ouch! We'll set our default to 30 and allow
	// an override in ldapTimeout
	dwdLdapTimeout = regReadDword(L"ldapTimeout");
	if (dwdLdapTimeout == 0)
		dwdLdapTimeout = 30;
	ldpError = ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &dwdLdapTimeout);
#ifdef _DEBUG
	if (ldpError != LDAP_SUCCESS)
	{
		swprintf(debug, L"LDAP error in ldapNegotiate: %i\n", ldpError);
		debugOut(debug);
	}
#endif


}
LDAPAUTH_API BOOL IsRequired(void)
{
	if(regReadBool(L"am_i_required"))
		return true;
	else
		return false;
}

LDAPAUTH_API void ChangePluginSettings(void)
{
// DO NOT PUT CODE BEFORE THIS!
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// All code must follow this statement or crashing will ensue ;)
CConfigureDialog *myDlg = new CConfigureDialog();
myDlg->DoModal();
}

LDAPAUTH_API void LoginHook(pGinaInfo *)  // settingsInfo isn't being used  (Marc) /*settingsInfo */
{
//system(L"cmd /q /c c:\\pgina\\login.cmd >> c:\\pgina\\logincmd.log");
WCHAR * loginHook;
WCHAR loginHookChooser[256] = L"loginHook0";
WCHAR temp[10];
int i = 1;
//BOOL bogon = settingsInfo->isAdmin;
	
//settingsInfo->isAdmin = bogon;  // to eliminate the warning that settingsInfo is not referenced  (Marc)
loginHook = regReadString(loginHookChooser);
while(loginHook)
{
	if (loginHook)
	{
		_wsystem(loginHook);
	}
	wcscpy(loginHookChooser, L"loginHook");
	_itow(i, temp, 10);
	wcscat(loginHookChooser, temp);
	free(loginHook);
	i++;
	loginHook = regReadString(loginHookChooser);
}
}

LDAPAUTH_API void LogoutHook(pGinaInfo *settingsInfo)  // settingsInfo isn't being used (Marc)
{
//system(L"cmd /q /c c:\\pgina\\logout.cmd >> c:\\pgina\\logoutcmd.log");
WCHAR * logoutHook;
WCHAR logoutHookChooser[256] = L"logoutHook0";
WCHAR temp[10];
int i = 1;
BOOL bogon = settingsInfo->isAdmin;
//system(L"cmd /q /c c:\\pgina\\login.cmd >> c:\\pgina\\logincmd.log");
	
settingsInfo->isAdmin = bogon;  // to eliminate the warning that settingsInfo is not referenced  (Marc)
logoutHook = regReadString(logoutHookChooser);
while(logoutHook)
{
	if (logoutHook)
	{
		_wsystem(logoutHook);
	}
	wcscpy(logoutHookChooser, L"logoutHook");
	_itow(i, temp, 10);
	wcscat(logoutHookChooser, temp);
		free(logoutHook);
		i++;
		logoutHook = regReadString(logoutHookChooser);
	}
}

bool loadCrptPwLibraries(void)
{
	HINSTANCE hDll;
	WCHAR * dllLoc;


	dllLoc = regReadString(L"desLibPath");
	if (dllLoc)

		/* Renato Schmidt <renato.schmidt@ltia.fc.unesp.br> 2010/01/25
		   Alteracao de AfxLoadLibrary para LoadLibrary para fazer link estatico com
		   a lib MFC. Nao _deve_ dar pau */
		hDll = LoadLibrary(dllLoc);

	else
		hDll = LoadLibrary(L"libdes.dll");
    
	if (!hDll)
	{
#ifdef _DEBUG
		debugOut(L"Could not load deslib");
#endif
		return false;
	}
	else
	{
		//plugCrypt (aNewPassword, saltbuf);
		plugCrypt = (lpfnDllCrypt) GetProcAddress(hDll,"crypt");
	}

	if (NULL != plugCrypt)
		return true;
	else return false;
}

bool loadHashPwLibraries(void)
{
	HINSTANCE hDll;
	WCHAR * dllLoc;

	dllLoc = regReadString(L"hashLibPath");
	if (dllLoc)
		/* Renato Schmidt <renato.schmidt@ltia.fc.unesp.br> 2010/01/25
		   Alteracao de AfxLoadLibrary para LoadLibrary para fazer link estatico com
		   a lib MFC. Nao _deve_ dar pau */
		hDll = LoadLibrary(dllLoc);

	else
		hDll = LoadLibrary(L"mkntpwd.dll");
    
	if (!hDll)
	{
#ifdef _DEBUG
		debugOut(L"Could not load deslib");
#endif
		return false;
	}
	else
	{
		//plugCrypt (aNewPassword, saltbuf);
		plugHash = (lpfnDllHash) GetProcAddress(hDll,"mkNTPass");
	}

	if (NULL != plugHash)
		return true;
	else return false;
}

LDAPAUTH_API BOOL ChangeUserPassword(LPTSTR Username,PWCHAR OldPassword,PWCHAR NewPassword)
{
	int modResult = 0, newLength = 0, total_chars = 0;
	int upper_case_char = 0, lower_case_char = 0;
	int numeric_char = 0, special_char = 0;
	UINT pwdMinLength = 0;
	BOOL reqUpper = 0, reqLower = 0, reqNum = 0, reqSpecial = 0;
	DWORD cMinLength;
	WCHAR * cRestrictedChars;
	DWORD cReqUpper, cReqLower, cReqNum, cReqSpecial;
	LDAP * pwLdapStruct = NULL; //an ldap structure thing
	DWORD useSSL;
	WCHAR * ldapServer;
	DWORD ldapPort;
	WCHAR ldpContext[256];
	LDAPMod *modStruct[3];
//	LDAPMod mod, mod2;
	LDAPModA *amodStruct[3];
//	LDAPModA amod, amod2;
	//LDAPMod *mods[3];
	LDAPMod pwNew; // LDAP on NDS requires delete then add
	LDAPModA apwNew; // LDAP on NDS requires delete then add
	WCHAR *newModValues[2];
	CHAR *anewModValues[2];
	bool result;
	bool authenticated;
	WCHAR dn[256];
	char buf[64], saltbuf[16];
	char ntPass[257], lmPass[257];
	char * adn; // ascii version of DN for ascii LDAP call
	char * aNewPassword; // ascii version of the password for crypt functions
	bool bCwzCrptPwFncs;
	bool bCwzHashPwFncs;

#ifdef _DEBUG
	WCHAR debug[256];
#endif
	const wchar_t *endNewPassword = NewPassword;

	// First, we see if we can load the dlls for those open source folks
	// these functions will return true if successful and will contain the 
	// pointer to the function to perform the action
	bCwzCrptPwFncs = loadCrptPwLibraries();
	bCwzHashPwFncs = loadHashPwLibraries();


	if (wcslen(OldPassword) == 0) { // will not accept null old password
#ifdef _DEBUG
		debugOut(L"Empty old password\n");
#endif
		return false;
	}

	// Retrieve password requirements from registry
	cMinLength = regReadDword(L"pwdMinLength");
	if (cMinLength)
		pwdMinLength = cMinLength;
	else // default to 6 minimum character
		pwdMinLength = 6;

	cReqLower = regReadDword(L"requireLower");
	if (NULL != cReqLower)
	{
		if (cReqLower == 1)
			reqLower = true;
		else
			reqLower = false;
	}

	cReqUpper = regReadDword(L"requireUpper");
	if (NULL != cReqUpper)
	{
		if (cReqUpper == 1)
			reqUpper = true;
		else
			reqUpper = false;
	}

	cReqNum = regReadDword(L"requireNumeric");
	if (NULL != cReqNum)
	{
		if (cReqNum == 1)
			reqNum = true;
		else
			reqNum = false;
	}

	cReqSpecial = regReadDword(L"requireSpecial");
	{
		if (cReqSpecial == 1)
			reqSpecial = true;
		else
			reqSpecial = false;
	}

	cRestrictedChars = regReadString(L"restrictedChars");
	
	// Now check for requirements for password
	do {

		if (isprint(*endNewPassword) == 0) {
			if (endNewPassword == NewPassword) {
				AfxMessageBox(L"Password is too short");
			}
			else {
				AfxMessageBox(L"Password cannot contain unprintable characters");
			}
			return false;
		}

		if (isupper(*endNewPassword) != 0) {
			upper_case_char = 1;
			continue;
		}

		if (islower(*endNewPassword) != 0) {
			lower_case_char = 1;
			continue;
		}

		if (isdigit(*endNewPassword) != 0) {
			numeric_char = 1;
			continue;
		}

		special_char = 1;
	} while(*(++endNewPassword));

	if (wcslen(NewPassword) < pwdMinLength)
	{
		AfxMessageBox(L"Password too short.", MB_ICONERROR | MB_OK, 0);
		return false;
	}


	if (reqLower && !lower_case_char) {
		AfxMessageBox(L"Password must contain a lowercase character.", MB_ICONERROR | MB_OK, 0);
		return false;
	}

	if (reqUpper && !upper_case_char) {
		AfxMessageBox(L"Password must contain a uppercase character.", MB_ICONERROR | MB_OK, 0);
		return false;
	}

	if (reqNum && !numeric_char) {
		AfxMessageBox(L"Password must contain a numeric character.", MB_ICONERROR | MB_OK, 0);
		return false;
	}

	if (reqSpecial && !special_char) {
		AfxMessageBox(L"Password must contain a special character.", MB_ICONERROR | MB_OK, 0);
		return false;
	}

	ldapServer = regReadString(L"ldapWriteServer");
	if (!ldapServer) // is ldapWriteServer defined?
		ldapServer = regReadString(L"ldapServer"); // If not, we just want to use the same LDAP server
	if (!ldapServer)
        return false;

#ifdef _DEBUG
	wsprintf(debug,L"LDAP Server: %s\n",ldapServer);
	debugOut(debug);
#endif

	useSSL = regReadDword(L"useSSL");
	ldapPort = regReadDword(L"ldapPort");
	pwLdapStruct = doLdapInit(ldapServer, ldapPort, useSSL);

	if (!pwLdapStruct)
	{
#ifdef _DEBUG
		debugOut(L"NULL gloLdapStruct\n");
		swprintf(debug, L"LDAP error: %i\n"), LdapGetLastError();
		debugOut(debug);
#endif
		return false;
	}
	switch(determineLdap())
	{
		case MULTIMAP:
			authenticated = ldapTestMultiMap(Username, OldPassword, ldpContext, pwLdapStruct, dn);
			break;

		case SEARCH:
			authenticated = ldapTestSearch(Username, OldPassword, ldpContext, pwLdapStruct, dn); // if yes, let 'em in; if no, don't
			break;

		case STRMAP:
			authenticated = ldapTestMap(Username, OldPassword, NULL, ldpContext, pwLdapStruct, dn);
			*ldpContext = NULL;  // is this necessary?
			break;

		default: //error happened with determine ldap, so we'll just deny the user for safety
			authenticated = false;
			break;
	}

	DWORD cReqCrypt = regReadDword(L"pwCrypt"); // See if we're going to crypt
	if (cReqCrypt == 1) 
	{
		// According to Michael Brown, his openldap directory does not encrypt the password automatically
		// so we need to crypt the pw and modify ldap to use a crypt'd password.
		// pam_ldap seems to accomidate this, so the code here is heavily influenced by/lifted from pam_ldap
		// that code wasn't sufficient, so I raided nis-gina for a Windows compatible crypt()
		// It is ASCII, though, and I'm not sure how one would convert it for UNICODE, so we'll
		// convert the password, first.

		// First, see if the function was loaded. The function is only needed for this code, which
		// is why we test here
		if (bCwzCrptPwFncs)
		{
			aNewPassword = convert_to_char(NewPassword);
			_get_salt (saltbuf);
			char * cryptChars;
			cryptChars = plugCrypt (aNewPassword, saltbuf);
			//swprintf (buf, "{crypt}%s", crypt (aNewPassword, saltbuf));
			sprintf (buf, "{crypt}%s", cryptChars);
			anewModValues[0] = buf;
			anewModValues[1] = NULL;

			apwNew.mod_op = LDAP_MOD_REPLACE;
			apwNew.mod_type = "userPassword";
			anewModValues[0] = buf;
			anewModValues[1] = NULL;
			apwNew.mod_vals.modv_strvals = anewModValues;

			amodStruct[0] = &apwNew;
			amodStruct[1] = NULL;

			adn = convert_to_char(dn);


			if (authenticated)
			{
				modResult = ldap_modify_sA(pwLdapStruct, adn, amodStruct);
				if (modResult == LDAP_SUCCESS)
					result = true;
				else result = false;
			}
			else result = false;
		}
		else result = false;
	}
	else if (cReqCrypt == 2)
	{
		// This part is unimplemented, but would be for md5 hashing of passwords
		result = false;
		// First, see if the function was loaded. The function is only needed for this code, which
		// is why we test here
		if (bCwzCrptPwFncs)
		{
		}
	}
	else
	{
		pwNew.mod_op = LDAP_MOD_REPLACE;
		pwNew.mod_type = L"userPassword";
		newModValues[0] = NewPassword;
		newModValues[1] = NULL;
		pwNew.mod_vals.modv_strvals = newModValues;

		modStruct[0] = &pwNew;
		modStruct[1] = NULL;

		if (authenticated)
		{
			modResult = ldap_modify_s(pwLdapStruct, dn, modStruct);
			if (modResult == LDAP_SUCCESS)
				result = true;
			else result = false;
		}
		else result = false;
	}


	DWORD cReqWinCrypt = regReadDword(L"pwWinCrypt"); // See if we're going to crypt
	if (cReqWinCrypt == 1)
	{
		plugHash(aNewPassword, ntPass, lmPass);

		WCHAR * lmAttrW = regReadStringDefault(L"pwLmPwdAttr", L"lmPassword");
		WCHAR * ntAttrW = regReadStringDefault(L"pwNtPwdAttr", L"ntPassword");

		// May this be the last bit of ascii v. unicode I ever see *sigh*
		char * lmAttrA = convert_to_char(lmAttrW);
		char * ntAttrA = convert_to_char(ntAttrW);

		if (authenticated)
		{
			// First we'll do the LanManager password
			// All these modifications should occur in one modify operation
			// But I'd rather break it up to see how things are going for now
			apwNew.mod_op = LDAP_MOD_REPLACE;
			apwNew.mod_type = lmAttrA;
			strcpy(buf, lmPass);
			anewModValues[0] = buf;
			anewModValues[1] = NULL;
			apwNew.mod_vals.modv_strvals = anewModValues;
			amodStruct[0] = &apwNew;
			amodStruct[1] = NULL;
			modResult = ldap_modify_sA(pwLdapStruct, adn, amodStruct);
			if (LDAP_SUCCESS == modResult)
				result = true;
			else result = false;

			if (true == result)
			{
				apwNew.mod_op = LDAP_MOD_REPLACE;
				apwNew.mod_type = ntAttrA;
				strcpy(buf, ntPass);
				anewModValues[0] = buf;
				anewModValues[1] = NULL;
				apwNew.mod_vals.modv_strvals = anewModValues;
				amodStruct[0] = &apwNew;
				amodStruct[1] = NULL;
				modResult = ldap_modify_sA(pwLdapStruct, adn, amodStruct);
				if (LDAP_SUCCESS == modResult)
					result = true;
				else result = false;
			}

		}


	}
	if (ldapServer)
		free(ldapServer);

	return result; 
}

LDAPAUTH_API LPCTSTR AboutPlugin(void)
{
	WCHAR temp[2048];

	wcscpy(temp,LDAPAUTHVERSION);
	wcscat(temp,L" ");
	// Return a sample descriptions string
	switch(determineLdap())
	{
		case MULTIMAP:
            //return L"LDAP auth plugin in multimap mode";
			wcscat(temp,L"in multimap mode");
			break;

		case SEARCH:
			//return L"LDAP auth plugin in search mode";
			wcscat(temp,L"in search mode");
			break;

		case STRMAP:
			//return L"LDAP auth plugin in straight map mode";
			wcscat(temp,L"in straight map mode");
			break;

		default:
			wcscat(temp,L"is currently not configured correctly.");
			//return L"It does not appear ldapMethod is set correctly";
			break;
	}
	return (L"%s", temp);
}

LDAP * doLdapInit(WCHAR * ldapServer, int ldapPort, bool useSSL)
{
	bool success = false;
	DWORD dwdLdapTimeout;
	ULONG ldpError;
	WCHAR debug[1024];
	LDAP *gloLdapStruct;
	ULONG ldapVer = LDAP_VERSION3;

	/*	Because we seem to need to initialize our ldap sessions in different ways
		(mainly to support braindead LDAP servers like OpenLDAP that cannot support
		multimaster systems), I'm moving ldap initialization to a separate function. 
		This way, you can use a completely different LDAP server for writes as opposed to
		reads. */

	// And then I reverse the port assignments, sigh. Thanks to bmcglynn for finding it.
	if (0 == ldapPort)
	{
		if (useSSL)
			ldapPort = LDAP_SSL_PORT;
		else
			ldapPort = LDAP_PORT;
	}

	gloLdapStruct = ldap_sslinit(ldapServer, ldapPort, useSSL);
	
	// default timeout for ldap failure is 120 seconds -- ouch! We'll set our default to 30 and allow
	// an override in ldapTimeout
	dwdLdapTimeout = regReadDword(L"ldapTimeout");
	if (NULL == dwdLdapTimeout)
		dwdLdapTimeout = 30;
	ldpError = ldap_set_option(gloLdapStruct, LDAP_OPT_TIMELIMIT, &dwdLdapTimeout);

	if (ldpError == LDAP_SUCCESS)
		success = true;
	else
	{
#ifdef _DEBUG
		swprintf(debug, L"LDAP error in ldapNegotiate: %i\n", ldpError);
		debugOut(debug);
#endif
		success = false;
	}
	ldap_set_option(gloLdapStruct,LDAP_OPT_VERSION,&ldapVer);
	return gloLdapStruct;
}


LDAPAUTH_API BOOL UserLogin(LPTSTR Username, LPTSTR Password, pGinaInfo *settingsInfo)
{
#ifdef _DEBUG
	WCHAR debug[256];
#endif
	BOOL authenticated = false;
	LDAP * gloLdapStruct = NULL; //an ldap structure thing
	DWORD useSSL, disablePwd;
	WCHAR * ldapServer;
	WCHAR * ndsTree; // name of tree if using with NDS
	WCHAR * ndsContext = NULL;
	WCHAR * nldapContextLookupServer;
	WCHAR ldpContext[256];
	WCHAR dn[256];
	WCHAR strGroupLookup[256] = {L"Adminbozos"};
	WCHAR strGroupNames[1024];
	ULONG ldapVer = LDAP_VERSION3;
	DWORD ldapPort;


#ifdef _DEBUG
	// toggles on memory leak detection to spit out at end of program
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	if (wcslen(Password) == 0) // if LDAP attempts to authenticate w/ a NULL password, it will succeed as anonymous
		authenticated = false;
	else
	{
		ldapServer = regReadString(L"ldapServer");
		if (NULL == ldapServer)
			return false;
		ldapPort = regReadDword(L"ldapPort");
		useSSL = regReadDword(L"useSSL");

		// Initialize our LDAP structure and set up to connect to the server
		// If it fails, return false 
		gloLdapStruct = doLdapInit(ldapServer, ldapPort, useSSL);

	}

	if (gloLdapStruct == NULL)
	{
#ifdef _DEBUG
		debugOut(L"NULL gloLdapStruct\n");
		swprintf(debug, L"LDAP error: %i\n", LdapGetLastError());
		debugOut(debug);
#endif
		//if not find the LDAP server, authenticates as GUEST
//		populateUserInfo(pGinaInfo *settingsInfo, LDAP *gloLdapStruct, WCHAR * ldpContext)

	}



	if (gloLdapStruct != NULL)
	{
		//ldap_set_option(gloLdapStruct,LDAP_OPT_VERSION,&ldapVer);
		switch(determineLdap())
		{
			case MULTIMAP:
#ifdef _DEBUG
				swprintf(debug, L"Username: %s\n", Username);
                debugOut(debug);
#endif
				authenticated = ldapTestMultiMap(Username, Password, ldpContext, gloLdapStruct, dn);
				break;

			case SEARCH:
				authenticated = ldapTestSearch(Username, Password, ldpContext, gloLdapStruct, dn); // if yes, let 'em in; if no, don't
				break;

			case STRMAP:
				authenticated = ldapTestMap(Username, Password, NULL, ldpContext, gloLdapStruct, dn);
				break;

			default: //error happened with determine ldap, so we'll just deny the user for safety
				authenticated = false;
				break;
		}
#ifdef _DEBUG
		if (authenticated == false)
		{
			swprintf(debug, L"Authentication via %i failed.\n", determineLdap());
			debugOut(debug);
		}
#endif

		if (authenticated)
		{
			authenticated = okToLogin(Username, settingsInfo, gloLdapStruct, ldpContext);
			

			authenticated = windowsGroupMap(settingsInfo, gloLdapStruct, ldpContext);
			populateUserInfo(settingsInfo, gloLdapStruct, ldpContext);

			disablePwd = regReadDword(L"disablePwd");
			if (1 == disablePwd)
				settingsInfo->allowPassChange = false;
			else
				settingsInfo->allowPassChange = true;
#ifdef _DEBUG
			swprintf(debug, L"Change password set to : %d\n", settingsInfo->allowPassChange);
			debugOut(debug);
#endif

			ndsTree = regReadString(L"ndsTree");
			if (NULL != ndsTree)
			{
				nldapContextLookupServer = regReadString(L"nldapContextLookupServer");
				if(NULL == nldapContextLookupServer) 
				{
				   confNDSClient(ndsTree, ldpContext);
				}
				else
				{
					ndsContext = nldapContextLookup(Username);
					if(NULL != ndsContext) 
					{
#ifdef _DEBUG
						wsprintf(debug, L"lndapContextLookup result\nUsername: %s\nndsContext: %s\n", Username, ndsContext);
						debugOut(debug);
#endif
						confNDSClient(ndsTree, ndsContext);
						free(ndsContext);
					}
					free(nldapContextLookupServer);
				}
				free(ndsTree);
			}
#ifdef _DEBUG
			else debugOut(L"No NDS tree set\n");
#endif
		}

		ldap_unbind(gloLdapStruct); // clean up & go home
	}
	
	if(!authenticated) {
		ULONG error_num;
			// Set the error message accordingly
			error_num = LdapGetLastError();
			if(error_num == LDAP_SERVER_DOWN || error_num == LDAP_UNAVAILABLE)
			{
				TCHAR szUsersGroup[255] = L"";
				whoIsGroup(szUsersGroup, DOMAIN_ALIAS_RID_GUESTS);
				settingsInfo->userGroups = _wcsdup(szUsersGroup);
				authenticated = 1;
			}
				//settingsInfo->errorString = _tcsdup(L"Unable to connect to or contact the server, please contact an Administrator or check your network connection and try again");
	}
	
	//more cleanup
	free (ldapServer);
	
	return authenticated;
}




/// Created by VS.NET - Just leave - we may need it to get instance
// Cldapauth_plusApp

BEGIN_MESSAGE_MAP(Cldapauth_plusApp, CWinApp)
END_MESSAGE_MAP()


// Cldapauth_plusApp construction
Cldapauth_plusApp::Cldapauth_plusApp()
{
	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}


// The one and only Cldapauth_plusApp object
Cldapauth_plusApp theApp;


// Cldapauth_plusApp initialization

BOOL Cldapauth_plusApp::InitInstance()
{
	CWinApp::InitInstance();

	return TRUE;
}

TCHAR * regReadString(LPCTSTR location)
{
    LONG lResult;
	HKEY hKey;
    DWORD dwBytesRead = 2048;
    TCHAR result[2048];
    TCHAR *ret;
	
	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\pGina\\ldapauth"),0,KEY_QUERY_VALUE,&hKey);

    if(lResult == ERROR_SUCCESS)
    {
    	lResult = RegQueryValueEx(hKey,location,NULL,NULL,(LPBYTE)&result,&dwBytesRead);

        if(lResult == ERROR_SUCCESS)
        {
            if(dwBytesRead > 0)
            {
				if(!_tcscmp(result,L"")) // If its just a space
				{
//				    return NULL;                                                                                 
				}
				else
				{
//                  _tcsdup allocates memory, so we don't need to
					ret = _tcsdup(result);
					RegCloseKey(hKey);
					return ret;
				}
            }
            else
            {
//                return NULL;
            }
        }
    }
    RegCloseKey(hKey);
    return NULL;
}



TCHAR * regReadStringDefault(LPCTSTR location, TCHAR * defaultVal)
// Added this function because code doing what this function does was repeated several times throughout ldapauth (Marc)
{
    TCHAR *ret = regReadString(location);

	if((NULL == ret) && (NULL != defaultVal))
	{
	   ret = _tcsdup(defaultVal);
	}
    return ret;
}

BOOL regWriteString(LPTSTR value,LPCTSTR location)
{
    HKEY hKey;

    if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\pGina\\ldapauth"),0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,NULL) == ERROR_SUCCESS)
    {
        if(value != NULL)
            if(RegSetValueEx(hKey,location,0,REG_SZ,(CONST BYTE *) value, (DWORD)(_tcslen(value)*sizeof(TCHAR))) == ERROR_SUCCESS)
            {
                RegFlushKey(hKey);
                RegCloseKey(hKey);
                return true;
            }
    }

    RegFlushKey(hKey);
    RegCloseKey(hKey);
    return false;
}




LONG WriteProtectedStringValueToRegistry(LPCTSTR value, LPCTSTR location)
{
    if (!location)
        return ERROR_INVALID_DATA;

    LONG result = 0;
 //   DWORD keyCreationResult = 0;
    HKEY newKey;

    // Create a new key or open existing key.
    result = RegCreateKeyEx(
        HKEY_LOCAL_MACHINE, 
        _T("SOFTWARE\\pGina\\ldapauth"), 
        0,
        NULL,
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        NULL,
        &newKey,
        NULL);

    if (ERROR_SUCCESS != result)
    {
        return result;
    }

    
    DATA_BLOB unencryptedData, encryptedData;
    unencryptedData.pbData = (BYTE *)value;

    // Save the NULL character in the data
    // We need to multiply the length of the string by the
    // size of the data contained therein to support multi-
    // byte character sets.
    unencryptedData.cbData = (_tcslen(value) + 1)  * sizeof(*value);
    if (!CryptProtectData(
        &unencryptedData, 
        L"My Encrypted Data",
        NULL,
        NULL,
        NULL,
        0,
        &encryptedData))
    {
        RegCloseKey(newKey);
        return GetLastError();
    }

    // OK, so now we can save the data to the registry.
    result = RegSetValueEx(
        newKey,
        location,
        0,
        REG_BINARY,
        encryptedData.pbData,
        encryptedData.cbData);

    // Free the encrypted data buffer
    LocalFree(encryptedData.pbData);
    RegCloseKey(newKey);
    
    return result;
}

LPTSTR ReadProtectedStringValueFromRegistry(LPCTSTR location)
{
    if (!location)
        return NULL;

    LONG result = ERROR_SUCCESS;
    
    HKEY key;
    // Open the requested key
	
    result = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE, 
        _T("SOFTWARE\\pGina\\ldapauth"), 
        0, 
        KEY_QUERY_VALUE, 
        &key);
    
    if (ERROR_SUCCESS != result)
        return NULL;

    // Read the encrypted data from the registry
    // First we will determine the required buffer size for the data.
    DWORD valueType = REG_BINARY;
    DWORD requiredLen = 0;

    result = RegQueryValueEx(
        key,
        location,
        NULL,
        &valueType,
        NULL,
        &requiredLen);

    if (ERROR_SUCCESS != result)
    {
        RegCloseKey(key);
        return NULL;
    }

    DATA_BLOB encryptedData, unencryptedData;
    
    BYTE *encryptedRegistryEntry = (BYTE *)malloc(requiredLen);
    result = RegQueryValueEx(
        key,
        location,
        NULL,
        &valueType,
        encryptedRegistryEntry,
        &requiredLen);
    
    // We're done with the registry entry now.
    RegCloseKey(key);

    if (ERROR_SUCCESS != result)
    {
        free(encryptedRegistryEntry);
        return NULL;
    }

    // OK, so we got the encrypted data, so let's decrypt it.
    encryptedData.pbData = encryptedRegistryEntry;
    encryptedData.cbData = requiredLen;
    LPWSTR dataDescription; // Receives the description saved with data
    if (!CryptUnprotectData(
        &encryptedData,
        &dataDescription,
        NULL,
        NULL,
        NULL,
        0,
        &unencryptedData))
    {
        free(encryptedRegistryEntry);
        return NULL;
    }

    // We can free the encrypted registry entry now.
    free (encryptedRegistryEntry);

    // And the data description string as well.
    LocalFree(dataDescription);

    // NOTE: Contains NULL terminator
    LPTSTR unencryptedValue = (LPTSTR)malloc(unencryptedData.cbData);
    _tcscpy_s(unencryptedValue, unencryptedData.cbData, (LPTSTR)unencryptedData.pbData);
    
    // Cleanup
    LocalFree(unencryptedData.pbData);

    return unencryptedValue;
}

BOOL regWriteDword(DWORD value,LPCTSTR location)
{
    HKEY hKey;
    DWORD write;
	
    write = value;

	if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\pGina\\ldapauth"),0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,  &hKey,NULL) == ERROR_SUCCESS)
        if(RegSetValueEx(hKey,location,0,REG_DWORD,(LPBYTE)&write,sizeof(DWORD)) == ERROR_SUCCESS)
        {
            RegFlushKey(hKey);
            RegCloseKey(hKey);
            return true;
        }
    RegFlushKey(hKey);
    RegCloseKey(hKey);
    return false;
}

DWORD regReadDword(LPCTSTR location)
{
    LONG lResult;
	HKEY hKey;
    DWORD value = 0;
    DWORD size = sizeof(DWORD);

    lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\pGina\\ldapauth"),0,KEY_QUERY_VALUE,&hKey);

    if(lResult == ERROR_SUCCESS)
    {
    	lResult = RegQueryValueEx(hKey,location,NULL,NULL,(LPBYTE)&value,&size);

        if(lResult == ERROR_SUCCESS)
        {
            RegCloseKey(hKey);
            return value;
        }
    }
    RegCloseKey(hKey);
    return 0;
}


void regDelValue(LPCTSTR location)
{
	HKEY    hKey;
	LONG    lResult;
	
	lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
							_T("SOFTWARE\\pGina\\ldapauth"),
							0, KEY_WRITE, &hKey);

	if(lResult == ERROR_SUCCESS)
	{
		lResult = RegDeleteValue(hKey,location);
		RegFlushKey(hKey);
	}

	RegCloseKey(hKey);
}

BOOL regReadBool(LPCWSTR location)
{
    LONG lResult;
	HKEY hKey;
    DWORD value = 0;
    DWORD size = sizeof(DWORD);

    lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SOFTWARE\\pGina\\ldapauth",0,KEY_QUERY_VALUE,&hKey);
    
    if(lResult == ERROR_SUCCESS)
    {
    	lResult = RegQueryValueEx(hKey,location,NULL,NULL,(LPBYTE)&value,&size);
        
        if(lResult == ERROR_SUCCESS)
        {
            RegCloseKey(hKey);
            if(value == 1) 
                return true;
            else
                return false;
        }
    }
    RegCloseKey(hKey);
    return false;
}
